package study.alex.resumedb.gae;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.oauth2.Oauth2;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserServiceFactory;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.server.ExternalResource;
import com.vaadin.ui.Component;
import com.vaadin.ui.Image;
import com.vaadin.ui.Link;
import com.vaadin.ui.Table;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created with IntelliJ IDEA.
 * User: alex
 * Date: 10.02.14
 * Time: 17:31
 * SVN $Id$
 */
public class FileItemBean implements Serializable {

    private final static Logger log = Logger.getLogger(FileItemBean.class.getName());

    private String id;
    private String title;
    private Link link;
    private Image icon;
    private Long size;
    private String type;

    /**
     * Default transportation layer for Google Apis Java client.
     */
    private static final HttpTransport TRANSPORT = new UrlFetchTransport();

    /**
     * Default JSON factory for Google Apis Java client.
     */
    private static final JsonFactory JSON_FACTORY = new JacksonFactory();



    public FileItemBean(File file) {
        if (file == null) return;
        id = file.getId();
        title = file.getTitle();
        type = file.getMimeType();
        Long sz = file.getFileSize();
        size = (sz == null) ? 0 : sz;
        String url = file.getAlternateLink();
        if (url == null) url = file.getDefaultOpenWithLink();
        if (url != null) link = new Link(title, new ExternalResource(url));
        else {
            link = new Link();
            link.setCaption(title);
        }
        url = file.getIconLink();
        if (url != null) {
            icon = new Image(title, new ExternalResource(url));
            icon.setWidth("20px");
            icon.setHeight("20px");
        }
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Link getLink() {
        return link;
    }

    public void setLink(Link link) {
        this.link = link;
    }

    public Image getIcon() {
        return icon;
    }

    public void setIcon(Image icon) {
        this.icon = icon;
    }

    public Long getSize() {
        return size;
    }

    public void setSize(Long size) {
        this.size = size;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    private Oauth2 getOauth2Service(Credential credential) {
        Oauth2 service = new Oauth2.Builder(TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName("ResumeDB WS").build();
        return service;
    }


    /**
     * Build and return a Drive service object based on given request parameters.
     * @param credential User credentials.
     * @return Drive service object that is ready to make requests, or null if
     *         there was a problem.
     */
    private Drive getDriveService(Credential credential) {
        return new Drive.Builder(TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName("ResumeDB WS").build();
    }

    private Credential getDiskCredentialForCurrentUser() {

        User user = UserServiceFactory.getUserService().getCurrentUser();

        CredentialManager manager = ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).getCredentialManager();

        if (manager == null || user == null) return null;

        try {
            if (manager.getStatus(user.getUserId()) == CredentialManager.GoogleDiskAccessStatus.GRANT) {
                Credential credential = ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).getCredential();
                return credential;
            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "Can't get credential from Datastore", e);
        }

        return null;
    }

    private Component getRootFilelist() {
        Credential credential = getDiskCredentialForCurrentUser();
        log.fine("Disk credential is " + credential);
        if (credential == null) return null;
        Drive gDisk = getDriveService(credential);
        List<File> fl;
        try {
            fl = retrieveRootFiles(gDisk);
        } catch (IOException e) {
            log.log(Level.SEVERE, "Exception when get filelist", e);
            return null;
        }

        Collections.sort(fl, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                return o1.getTitle().compareTo(o2.getTitle());
            }
        });

        List<FileItemBean> items = new ArrayList<FileItemBean>();
        for (File f : fl) items.add(new FileItemBean(f));

        BeanItemContainer<FileItemBean> data = new BeanItemContainer<FileItemBean>(FileItemBean.class, items);
        Table table = new Table("List of Root", data);
        table.setVisibleColumns("icon", "link", "type", "size");
        table.setColumnAlignments(Table.Align.CENTER, Table.Align.LEFT, Table.Align.CENTER, Table.Align.CENTER);
        table.setColumnHeader("link", "file");
        table.setColumnReorderingAllowed(true);
        table.setSizeUndefined();
        table.setPageLength(25);
        return table;
    }

    /**
     * Retrieve a list of File resources.
     *
     * @param service Drive API service instance.
     * @return List of File resources.
     */
    private List<File> retrieveAllFiles(Drive service) throws IOException {
        List<File> result = new ArrayList<File>();
        Drive.Files.List request = service.files().list().setQ("trashed=false");

        do {
            try {
                FileList files = request.execute();

                result.addAll(files.getItems());
                request.setPageToken(files.getNextPageToken());
            } catch (GoogleJsonResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                if (e.getStatusCode() == 401) {
                    // The user has revoked our token or it is otherwise bad.
                    // Delete the local copy so that their next page load will recover.
                    ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                }
                request.setPageToken(null);
                return result;
            } catch (TokenResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                request.setPageToken(null);
                return result;
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exception when get about info", e);
                request.setPageToken(null);
            }
        } while (request.getPageToken() != null &&
                request.getPageToken().length() > 0);

        return result;
    }
    /**
     * Retrieve a list of File resources in Root.
     *
     * @param service Drive API service instance.
     * @return List of File resources.
     * @throws java.io.IOException
     */
    private List<File> retrieveRootFiles(Drive service) throws IOException {

        return retrieveFilesInFolderById(service, "root");
    }



    /**
     * Retrieve a list of File resources in some Folder (by Id).
     *
     * @param service Drive API service instance.
     * @param folderId Folder Id
     * @return List of File resources.
     * @throws java.io.IOException
     */
    private List<File> retrieveFilesInFolderById(Drive service, String folderId) throws IOException {

        List<File> result = new ArrayList<File>();
        Drive.Files.List request = service.files().list().setQ("'" + folderId + "' in parents and trashed=false");

        do {
            try {
                FileList files = request.execute();

                result.addAll(files.getItems());
                request.setPageToken(files.getNextPageToken());
            } catch (GoogleJsonResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                if (e.getStatusCode() == 401) {
                    // The user has revoked our token or it is otherwise bad.
                    // Delete the local copy so that their next page load will recover.
                    ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                }
                request.setPageToken(null);
                break;
            } catch (TokenResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                request.setPageToken(null);
                break;
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exception when get about info", e);
                request.setPageToken(null);
            }
        } while (request.getPageToken() != null &&
                request.getPageToken().length() > 0);

        log.fine("Got folder list (folderId " + folderId + ").  Size is " + result.size());

        return result;
    }

    /**
     * Get a file's metadata by FileId.
     *
     * @param service Drive API service instance.
     * @param fileId ID of the file to get metadata for.
     * @return A File's metadata
     */
    private File getFile(Drive service, String fileId) {

        File file = null;

        try {
            file = service.files().get(fileId).execute();

        } catch (IOException e) {
            log.severe("An error occured: " + e);
        }

        return file;
    }
    /**
     * Get a file's metadata by Title.
     *
     * @param service Drive API service instance.
     * @param title title of the file to get metadata for.
     * @return List of files
     */
    private List<File> getFilesByTitle(Drive service, String title) throws IOException {

        List<File> result = new ArrayList<File>();
        Drive.Files.List request = service.files().list().setQ("title = '" + title +"' and trashed=false");

        do {
            try {
                FileList files = request.execute();

                result.addAll(files.getItems());
                request.setPageToken(files.getNextPageToken());
            } catch (GoogleJsonResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                if (e.getStatusCode() == 401) {
                    // The user has revoked our token or it is otherwise bad.
                    // Delete the local copy so that their next page load will recover.
                    ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                }
                request.setPageToken(null);
                break;
            } catch (TokenResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                request.setPageToken(null);
                break;
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exception when get about info", e);
                request.setPageToken(null);
            }
        } while (request.getPageToken() != null &&
                request.getPageToken().length() > 0);

        return result;
    }
    /**
     * Get a folder's metadata by Title.
     *
     * @param service Drive API service instance.
     * @param title title of the folder to get metadata for.
     * @return List of folders
     */
    private List<File> getFolderByTitle(Drive service, String title) throws IOException {

        List<File> result = new ArrayList<File>();
        Drive.Files.List request = service.files().list().setQ("title = '" + title
                +"and mimeType = 'application/vnd.google-apps.folder' and trashed=false");

        do {
            try {
                FileList files = request.execute();

                result.addAll(files.getItems());
                request.setPageToken(files.getNextPageToken());
            } catch (GoogleJsonResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                if (e.getStatusCode() == 401) {
                    // The user has revoked our token or it is otherwise bad.
                    // Delete the local copy so that their next page load will recover.
                    ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                }
                request.setPageToken(null);
                break;
            } catch (TokenResponseException e) {
                log.log(Level.WARNING, "Token revoked or it is otherwise bad", e);
                ((ResumeDbMainServlet)ResumeDbMainServlet.getCurrent()).dropCredential();
                request.setPageToken(null);
                break;
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exception when get about info", e);
                request.setPageToken(null);
            }
        } while (request.getPageToken() != null &&
                request.getPageToken().length() > 0);

        return result;
    }

    /**
     * Download a file's content.
     *
     * @param service Drive API service instance.
     * @param file Drive File instance.
     * @return InputStream containing the file's content if successful,
     *         {@code null} otherwise.
     */
    private InputStream downloadFile(Drive service, File file) {
        if (file.getDownloadUrl() != null && file.getDownloadUrl().length() > 0) {
            try {
                HttpResponse resp =
                        service.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl()))
                                .execute();
                return resp.getContent();
            } catch (IOException e) {
                // An error occurred.
                log.severe("Can't download file from Google Drive URL." + e);
                return null;
            }
        } else {
            // The file doesn't have any content stored on Drive.
            return null;
        }
    }

}

